<?php

namespace App\Http\Utils\Aip;

/*
 * Copyright (c) 2017 Baidu.com, Inc. All Rights Reserved
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * Http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

/**
 * BCE Util
 */
class AipHttpUtil {
	// 根据RFC 3986，除了：
	// 1.大小写英文字符
	// 2.阿拉伯数字
	// 3.点'.'、波浪线'~'、减号'-'以及下划线'_'
	// 以外都要编码
	public static $PERCENT_ENCODED_STRINGS;
	
	// 填充编码数组
	public static function __init() {
		AipHttpUtil::$PERCENT_ENCODED_STRINGS = array ();
		for($i = 0; $i < 256; ++ $i) {
			AipHttpUtil::$PERCENT_ENCODED_STRINGS [$i] = sprintf ( "%%%02X", $i );
		}
		
		// a-z不编码
		foreach ( range ( 'a', 'z' ) as $ch ) {
			AipHttpUtil::$PERCENT_ENCODED_STRINGS [ord ( $ch )] = $ch;
		}
		
		// A-Z不编码
		foreach ( range ( 'A', 'Z' ) as $ch ) {
			AipHttpUtil::$PERCENT_ENCODED_STRINGS [ord ( $ch )] = $ch;
		}
		
		// 0-9不编码
		foreach ( range ( '0', '9' ) as $ch ) {
			AipHttpUtil::$PERCENT_ENCODED_STRINGS [ord ( $ch )] = $ch;
		}
		
		// 以下4个字符不编码
		AipHttpUtil::$PERCENT_ENCODED_STRINGS [ord ( '-' )] = '-';
		AipHttpUtil::$PERCENT_ENCODED_STRINGS [ord ( '.' )] = '.';
		AipHttpUtil::$PERCENT_ENCODED_STRINGS [ord ( '_' )] = '_';
		AipHttpUtil::$PERCENT_ENCODED_STRINGS [ord ( '~' )] = '~';
	}
	
	/**
	 * 在uri编码中不能对'/'编码
	 *
	 * @param string $path        	
	 * @return string
	 */
	public static function urlEncodeExceptSlash($path) {
		return str_replace ( "%2F", "/", AipHttpUtil::urlEncode ( $path ) );
	}
	
	/**
	 * 使用编码数组编码
	 *
	 * @param string $path        	
	 * @return string
	 */
	public static function urlEncode($value) {
		$result = '';
		for($i = 0; $i < strlen ( $value ); ++ $i) {
			$result .= AipHttpUtil::$PERCENT_ENCODED_STRINGS [ord ( $value [$i] )];
		}
		return $result;
	}
	
	/**
	 * 生成标准化QueryString
	 *
	 * @param array $parameters        	
	 * @return array
	 */
	public static function getCanonicalQueryString(array $parameters) {
		// 没有参数，直接返回空串
		if (count ( $parameters ) == 0) {
			return '';
		}
		
		$parameterStrings = array ();
		foreach ( $parameters as $k => $v ) {
			// 跳过Authorization字段
			if (strcasecmp ( 'Authorization', $k ) == 0) {
				continue;
			}
			if (! isset ( $k )) {
				throw new \InvalidArgumentException ( "parameter key should not be null" );
			}
			if (isset ( $v )) {
				// 对于有值的，编码后放在=号两边
				$parameterStrings [] = AipHttpUtil::urlEncode ( $k ) . '=' . AipHttpUtil::urlEncode ( ( string ) $v );
			} else {
				// 对于没有值的，只将key编码后放在=号的左边，右边留空
				$parameterStrings [] = AipHttpUtil::urlEncode ( $k ) . '=';
			}
		}
		// 按照字典序排序
		sort ( $parameterStrings );
		
		// 使用'&'符号连接它们
		return implode ( '&', $parameterStrings );
	}
	
	/**
	 * 生成标准化uri
	 *
	 * @param string $path        	
	 * @return string
	 */
	public static function getCanonicalURIPath($path) {
		// 空路径设置为'/'
		if (empty ( $path )) {
			return '/';
		} else {
			// 所有的uri必须以'/'开头
			if ($path [0] == '/') {
				return AipHttpUtil::urlEncodeExceptSlash ( $path );
			} else {
				return '/' . AipHttpUtil::urlEncodeExceptSlash ( $path );
			}
		}
	}
	
	/**
	 * 生成标准化http请求头串
	 *
	 * @param array $headers        	
	 * @return array
	 */
	public static function getCanonicalHeaders($headers) {
		// 如果没有headers，则返回空串
		if (count ( $headers ) == 0) {
			return '';
		}
		
		$headerStrings = array ();
		foreach ( $headers as $k => $v ) {
			// 跳过key为null的
			if ($k === null) {
				continue;
			}
			// 如果value为null，则赋值为空串
			if ($v === null) {
				$v = '';
			}
			// trim后再encode，之后使用':'号连接起来
			$headerStrings [] = AipHttpUtil::urlEncode ( strtolower ( trim ( $k ) ) ) . ':' . AipHttpUtil::urlEncode ( trim ( $v ) );
		}
		// 字典序排序
		sort ( $headerStrings );
		
		// 用'\n'把它们连接起来
		return implode ( "\n", $headerStrings );
	}
}
AipHttpUtil::__init ();
class AipSignOption {
	const EXPIRATION_IN_SECONDS = 'expirationInSeconds';
	const HEADERS_TO_SIGN = 'headersToSign';
	const TIMESTAMP = 'timestamp';
	const DEFAULT_EXPIRATION_IN_SECONDS = 1800;
	const MIN_EXPIRATION_IN_SECONDS = 300;
	const MAX_EXPIRATION_IN_SECONDS = 129600;
}
class AipSampleSigner {
	const BCE_AUTH_VERSION = "bce-auth-v1";
	const BCE_PREFIX = 'x-bce-';
	
	// 不指定headersToSign情况下，默认签名http头，包括：
	// 1.host
	// 2.content-length
	// 3.content-type
	// 4.content-md5
	public static $defaultHeadersToSign;
	public static function __init() {
		AipSampleSigner::$defaultHeadersToSign = array (
				"host",
				"content-length",
				"content-type",
				"content-md5" 
		);
	}
	
	/**
	 * 签名
	 *
	 * @param array $credentials        	
	 * @param string $httpMethod        	
	 * @param string $path        	
	 * @param array $headers        	
	 * @param string $params        	
	 * @param array $options        	
	 * @return string
	 */
	public static function sign(array $credentials, $httpMethod, $path, $headers, $params, $options = array()) {
		// 设定签名有效时间
		if (! isset ( $options [AipSignOption::EXPIRATION_IN_SECONDS] )) {
			// 默认值1800秒
			$expirationInSeconds = AipSignOption::DEFAULT_EXPIRATION_IN_SECONDS;
		} else {
			$expirationInSeconds = $options [AipSignOption::EXPIRATION_IN_SECONDS];
		}
		
		// 解析ak sk
		$accessKeyId = $credentials ['ak'];
		$secretAccessKey = $credentials ['sk'];
		
		// 设定时间戳，注意：如果自行指定时间戳需要为UTC时间
		if (! isset ( $options [AipSignOption::TIMESTAMP] )) {
			// 默认值当前时间
			$timestamp = gmdate ( 'Y-m-d\TH:i:s\Z' );
		} else {
			$timestamp = $options [AipSignOption::TIMESTAMP];
		}
		
		// 生成authString
		$authString = AipSampleSigner::BCE_AUTH_VERSION . '/' . $accessKeyId . '/' . $timestamp . '/' . $expirationInSeconds;
		
		// 使用sk和authString生成signKey
		$signingKey = hash_hmac ( 'sha256', $authString, $secretAccessKey );
		
		// 生成标准化URI
		$canonicalURI = AipHttpUtil::getCanonicalURIPath ( $path );
		
		// 生成标准化QueryString
		$canonicalQueryString = AipHttpUtil::getCanonicalQueryString ( $params );
		
		// 填充headersToSign，也就是指明哪些header参与签名
		$headersToSign = null;
		if (isset ( $options [AipSignOption::HEADERS_TO_SIGN] )) {
			$headersToSign = $options [AipSignOption::HEADERS_TO_SIGN];
		}
		
		// 生成标准化header
		$canonicalHeader = AipHttpUtil::getCanonicalHeaders ( AipSampleSigner::getHeadersToSign ( $headers, $headersToSign ) );
		
		// 整理headersToSign，以';'号连接
		$signedHeaders = '';
		if ($headersToSign !== null) {
			$signedHeaders = strtolower ( trim ( implode ( ";", $headersToSign ) ) );
		}
		
		// 组成标准请求串
		$canonicalRequest = "$httpMethod\n$canonicalURI\n" . "$canonicalQueryString\n$canonicalHeader";
		
		// 使用signKey和标准请求串完成签名
		$signature = hash_hmac ( 'sha256', $canonicalRequest, $signingKey );
		
		// 组成最终签名串
		$authorizationHeader = "$authString/$signedHeaders/$signature";
		
		return $authorizationHeader;
	}
	
	/**
	 * 根据headsToSign过滤应该参与签名的header
	 *
	 * @param array $headers        	
	 * @param array $headersToSign        	
	 * @return array
	 */
	public static function getHeadersToSign($headers, $headersToSign) {
		
		// value被trim后为空串的header不参与签名
		$filter_empty = function ($v) {
			return trim ( ( string ) $v ) !== '';
		};
		$headers = array_filter ( $headers, $filter_empty );
		
		// 处理headers的key：去掉前后的空白并转化成小写
		$trim_and_lower = function ($str) {
			return strtolower ( trim ( $str ) );
		};
		$temp = array ();
		$process_keys = function ($k, $v) use (&$temp, $trim_and_lower) {
			$temp [$trim_and_lower ( $k )] = $v;
		};
		array_map ( $process_keys, array_keys ( $headers ), $headers );
		$headers = $temp;
		
		// 取出headers的key以备用
		$header_keys = array_keys ( $headers );
		
		$filtered_keys = null;
		if ($headersToSign !== null) {
			// 如果有headersToSign，则根据headersToSign过滤
			
			// 预处理headersToSign：去掉前后的空白并转化成小写
			$headersToSign = array_map ( $trim_and_lower, $headersToSign );
			
			// 只选取在headersToSign里面的header
			$filtered_keys = array_intersect_key ( $header_keys, $headersToSign );
		} else {
			// 如果没有headersToSign，则根据默认规则来选取headers
			$filter_by_default = function ($k) {
				return AipSampleSigner::isDefaultHeaderToSign ( $k );
			};
			$filtered_keys = array_filter ( $header_keys, $filter_by_default );
		}
		
		// 返回需要参与签名的header
		return array_intersect_key ( $headers, array_flip ( $filtered_keys ) );
	}
	
	/**
	 * 检查header是不是默认参加签名的：
	 * 1.是host、content-type、content-md5、content-length之一
	 * 2.以x-bce开头
	 *
	 * @param array $header        	
	 * @return boolean
	 */
	public static function isDefaultHeaderToSign($header) {
		$header = strtolower ( trim ( $header ) );
		if (in_array ( $header, AipSampleSigner::$defaultHeadersToSign )) {
			return true;
		}
		return substr_compare ( $header, AipSampleSigner::BCE_PREFIX, 0, strlen ( AipSampleSigner::BCE_PREFIX ) ) == 0;
	}
}
AipSampleSigner::__init ();
